package cn.com.flower.passport.config;

import cn.com.flower.common.enumerator.ServiceCode;
import cn.com.flower.common.web.JsonResult;
import cn.com.flower.passport.filter.JwtAuthorizationFilter;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import springfox.documentation.annotations.ApiIgnore;

import java.io.PrintWriter;

@Configuration// 标记此类是配置类
@EnableGlobalMethodSecurity(prePostEnabled = true)// 启用全局方法安全性
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthorizationFilter jwtAuthorizationFilter;

    @Bean// 使用Bean注解必须出现在配置类中，用来让Spring框架自动调用，并拿到返回结果保存到Spring容器里面
    // 验证密文需要BCrypt的PasswordEncoder.matches(原始密码, 密文密码)方法
    public PasswordEncoder passwordEncoder() {
        // 因为是工具，用自动装配可以避免new多个
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 允许跨域访问
        http.cors();

        // 调整Session创建策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 将自定义的过滤器放在框架过滤器之前
        http.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);

        // 处理未认证但访问需要认证显示的403问题
        http.exceptionHandling().authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
            // 设置响应内容类型JSON格式，并指定字符集为UTF-8
            httpServletResponse.setContentType("application/json; charset=UTF-8");
            // 设置返回消息
            String message = "Not logged in,Please log in again!";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERROR_UNAUTHORIZED, message);
            // 需要alibaba的fastJSON依赖来将对象转为JSON
            String jsonString = JSON.toJSONString(jsonResult);
            // 获取响应输出流
            PrintWriter writer = httpServletResponse.getWriter();
            // 将消息响应到客户端
            writer.println(jsonString);
            // 关闭输出流
            writer.close();
            return;
        });

        // 禁用“防止伪造的跨域攻击的防御机制”
        http.csrf().disable();

        // 白名单
        String[] urls = {
                "/doc.html",
                "/**/*.css",
                "/**/*.js",
                "/swagger-resources/**",
                "/v2/api-docs",
                "favicon.ico",
                "/users/login",
                "/tests/session/authenticate",
                "/tests/session/authorize"
        };

        // 配置请求授权
        http.authorizeRequests() // 开始配置授权访问
                .mvcMatchers(urls) // 匹配不需认证的请求，多个请求用逗号隔开
                .permitAll() // 许可，不需要通过认证就可以访问
                .anyRequest() // 任何请求都需要通过认证
                .authenticated() // 需要以上请求是已经通过认证的
        ;

        // 登录页
        // 调用fromLogin() 方法，表示启用登录页，当未认证时，那些需要通过认证的请求将被重定向到登录页
        // 不调用fromLogin() 方法，表示不启用登录页，当未认证时，访问那些需要认证的请求将会显示403
        // http.formLogin();
    }
}